﻿#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>



//<1>字符指针变量
//int main()
//{
//	char a = 'w';
//	char* p1 = &a;//存放字符的地址的指针变量就是字符指针变量
//	printf("%c\n", a);
//	*p1 = 'q';//单个字符可以修改
//	printf("%c ", *p1);
//
//
//	return 0;
//}

//字符指针变量（字符串）
//int main()
//{
//	char* ch = "abcdef";//字符指针变量也可以是存放字符串的地址的变量，注意这里指针存的是字符串第一个元素的地址
//						//计算机只要知道了字符串第一个元素的地址，就可以访问接下来字符串的每个字符
//	printf("%s\n", ch);//打印字符串的方式（1）
// 
//		//在使用字符串第一个字符的地址时，要分情况进行区分要不要*（解引用）	
//	    //如果用解引用操作，只会使用第一个元素a，因为ch只存了第一个元素的地址，
//		//如果不用解引用操作，传的是第一个字符的地址，就可以访问接下来字符串的每个字符
//	
// 
//	//这里不用解引用操作，一旦解引用，只会打印第一个元素a，因为ch只存了第一个元素的地址，
//	//但如果传的是第一个字符的地址，就可以访问接下来字符串的每个字符
//	//*ch = 'b';//注意：常量字符串不能修改
//	//printf("%s\n", ch);
//	printf("%s\n", "abcdef");//打印字符串的方式（2）
//	int i = 0;//创建指针变化的步长计算的工具
//	int r = strlen("abcdef");
//	for (i = 0; i < r; i++)
//	{
//		printf("%c", *(ch + i));//打印字符串的方式（3）
//	}
//
//	return 0;
//}


//int main()
//{
//	char str1[] = "hello bit.";//数组
//	char str2[] = "hello bit.";
//	const char* str3 = "hello bit.";//常量字符串
//	const char* str4 = "hello bit.";
//	if (str1 == str2)
//		printf("str1 and str2 are same\n");//1
//	else
//		printf("str1 and str2 are not same\n");//2	//1和2打印2，因为1，2都是数组，而if语句中的只有数组名，也就是数组首元素的地址，两个数组首元素的地址是不同的
//														//因为两个数组申请的内存空间不一样
//
//	if (str3 == str4)
//		printf("str3 and str4 are same\n");//3	//3和4选3，因为3，4都是常量字符串，会被存储到一个单独的内存区域，当几个指针同时指向1个常量字符串时，实际指向的是同一块内存
//	else
//		printf("str3 and str4 are not same\n");//4
//
//	return 0;
//}



//<2>数组指针
//int main()
//{
//	int arr[5] = { 1,2,3,4,5 };
//	int(*p)[5] = &arr;//数组指针：存放数组地址的指针，但是一个数组有多个元素，要存放多个元素的地址，所以加了[]表示指向数组元素的个数
//						//前面的(*p)是把指针与*结合强调p是一个指针变量，与指针数组区分：int * arr[5];没有括号的是指针数组，有括号的是数组指针
//						//前面的int 表示指针指向的数组元素类型
//
//	return 0;
//}

//
//int main()
//{
//	char arr1[5] = { 0 };//这是一个数组
//	//如果要存放数组的地址
//	char(*p)[5] = &arr1;//数组的初始化
//						//就写一个数组指针用来存放，指向数组元素的类型是char
//	//此时指针p的类型是数组指针类型，但必须说准确了：char(*)[5]
//
//	char* arr2[5] = { 0 };//这是一个指针数组，本质上就是一个数组
//	//如果也要存放这个数组的地址
//	char* (*p) = &arr2;//仍然写一个数组指针存放指针数组，但是指向数组元素的类型是char*
//	//此时指针p的类型是char*(*)[5]//[]里的数字不能省略
//	
//	//前面说讲的：
//	//arr
//	//arr+1	//跳过4个字节，跳过的字节大小是由前面的指针指向的类型决定的	int* 4个字节
//	
//	//&arr[0]
//	//&arr[0]+1//跳过4个字节,同上
//	
//	//&arr
//	//&arr+1//跳过40个字节，一样的，跳过的字节大小是由指针指向的类型决定的	int*[10] 4*10=40
//					//这里就可以发现[]中不能省略数字的意义
//
//	return 0;
//}



//前面学过用指针访问数组中每个元素的地址并打印
//int main()
//{
//	int arr[5] = { 1,2,3,4,5 };
//	int* p = &arr;
//	//int* p = arr;也可以
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *(p + i));//指针计算的步长是由前面的指针指向的类型决定的
//		//printf("%d ", *(&arr + i));//而地址的计算是跟指针不一样的，在指针的运算中，步长是只由前面的指针类型决定的，地址的计算的步长大小由地址本身的意义决定的
//										//比如：arr + 1;该地址的意义是数组首元素的地址，+1代表的是1个元素的步长
//										//而&arr + 1;该地址的意义是整个元素的地址，+1代表的是1个数组的步长
//							//所以在进行计算时，要分清是地址运算还是指针运算
//	}
//	return 0;
//}

//现在用数组指针来访问数组每个元素并打印
// int main()
//{
//	int arr[5] = { 1,2,3,4,5 };
//	int (*p)[5] = &arr;//这边取的是整个数组的地址，如果把这个指针拿去加减计算，步长是一整个数组，（因为类型是int(*)[5]）
//	//int(*p)[5] = arr;//也不行，因为类型没有改变，类型仍然是int(*)[5]，步长仍然是一整个数组
//
//	//注：在指针的运算中，步长是只由前面的指针类型决定的
//
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < sz; i++)
//	{
//		//printf("%d ", *(p + i));//在数组指针中，指针计算的步长大小是int(*)[5]，就是一个数组的大小，所以用这个方法是打印不了每个元素的
//		printf("%d ", (*p)[i]);//先解引用，表示数组每个元素，在用[i]下标访问，进行打印，但这种方法别扭
//	}
//	return 0;
//}
//arr：取的是数组首元素的地址
//&arr：取的是整个数组的地址，与首元素的地址一致，但是意义不同




//<3>二维数组传参的本质
//前面学过二维数组传参
//void test(int arr[2][3],int x,int y)
//{
//	int i = 0;
//	for (i = 0; i < x; i++)
//	{
//		int j = 0;
//		for (j = 0; j < y; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n");
//	}
//
//}
//
//int main()
//{
//	int arr[2][3] = { {1,2,3},{2,3,4}};
//	test(arr, 2, 3);//实参传数组名过去就可以
//
//	return 0;
//}

//接下来是揭示二维数组传参的本质
//void test(int (*p)[2], int x, int y)//既然传递的是二维数组第一行的地址，那我可以用数组指针来存放数组地址（指向的数组类型 + (*指针) + [i]([i]表示数组的元素个数)）
//																					//注：因为这里我已经把二维数组的元素理解为是一维数组，也就是每行，所以不用两个[]表示
//{
//	int i = 0;
//	for (i = 0; i < x; i++)
//	{
//		int j = 0;
//		for (j = 0; j < y; j++)
//		{
//			//printf("%d ", (*(p + i))[j]);//*(p + i)外要加（），不然*就会先解引用[j]，因为[]优先级更高
//			printf("%d ", *(*(p + i) + j));//也可以这样打印，先解引用行数*(p + i)，再解引用列数*(+ j)，分别进行解引用
//		}
//		printf("\n");
//	}
//
//}
//
//int main()
//{
//	int arr[2][3] = { {1,2,3},{2,3,4} };
//	test(arr, 2, 3);//二维数组传参传递的是数组名，前面讲过数组名是数组首元素的地址
//						//那么二维数组呢？
//						// 二维数组传参传递的也是数组首元素的地址，arr表示数组首元素的地址
//						// 而二维数组的元素是一维数组
//						// 所以二维数组传参传递的是第一个一维数组的地址
//						// 也就是二维数组第一行的地址，这就是二维数组传参的本质
//					//实参传数组名过去就可以
//
//	return 0;
//}

//为什么创建一个i就是数组的地址呢？
//int main()
//{
//	int arr[3] = { 1,2,3 };
//	int i = 0;
//	for (i = 0; i < 3; i++)
//	{
//		printf("%d ", arr[i]);//为什么创建一个i就是数组的地址呢？，因为i放在arr[]中里识别了，而arr[i]就是*(arr + i)，arr代表数组首元素的地址，+表示涉及计算
//								//这里明显是地址计算，地址计算的步长由地址本身的意义决定，arr表示了步长是一个元素，每加一个i，就加多少个元素，成为下一个元素的地址
//								//再解引用，访问地址所在的空间，也就是元素
//	}
//
//	return 0;
//}



//<4>函数指针
//int Add(int a, int b)
//{
//	return a + b;
//}
//int main()
//{
//	int a = 10;
//	int b = 20;
//	int num = Add(a, b);
//	//printf("%d", num);
//	printf("%p\n", &Add);
//	printf("%p\n", Add);
//	//会发现：&Add和Add打印的地址都是一样的
//	//好像与&arr和arr一样，但其实是不一样的，&Add和Add都是函数地址，而&arr和arr的意思表达就不一样了
//
//
//	return 0;
//}

//int Add(int a, int b)
//{
//	return a + b;
//}
//int main()
//{
//	int a = 10;
//	int b = 20;
//	int num = Add(a, b);
//	//上面明白了什么是函数地址，现在来弄清楚什么是函数指针
//	//int Add(int a, int b)
//	int (*p)(int, int) = &Add;//顾名思义：函数指针就是存放函数地址的指针变量
//								//格式：指向的函数类型 + (*指针变量) + (函数形参类型,函数形参类型...)就是代表指向的函数
//	//与数组指针类比
//	int arr[5] = { 1,2,3,4,5 };
//	int(*p2)[5] = &arr;//格式：指向的数组类型 + (*p2) + [i]就是代表指向的数组
//
//	return 0;
//}
//归纳：	所有的指针都包括两个要素：指向对象的类型 + 指向的对象
//		指针包括：指向对象的类型 + 指向的对象（就是*指针变量）
//		数组指针：指向的数组类型 + (*p2)[i]就是代表指向的对象是数组
//		函数指针：指向的函数类型 + (*指针变量)(函数形参类型,函数形参类型...)就是代表指向的对象是函数



//<5>函数指针的使用
//int Add(int a, int b)
//{
//	return a + b;
//}
//int main()
//{
//	int a = 10;
//	int b = 20;
//	Add(a, b);
//
//	//函数指针的初始化
//
//	int (*pA)(int, int) = &Add;
//	//函数指针的类型：把函数指针变量名去掉，就是函数指针的类型
//	//总结：指针的类型就是把初始化中左边表达式中的指针变量名去掉就是指针的类型
//	int (*pB)(int, int) = Add;
//
//	//函数指针的使用
//
//	int num = (*pA)(a, b);//总结：在使用函数指针时，要使用指向的对象，所有的指针都一样，要使用指针，就要使用指向的对象部分
//	//*pA外要加()，因为要与右边的函数参数区别开，就像数组指针的使用，(*p)[i]要加括号一样，要与右边的[i]数组元素区别开一样
//	//int num2 = (*pB)(a, b);
//	int num2 = (******pB)(a, b);
//	//int num2 = pB(a, b);
//	//注：这里的在使用的指向的对象(*pB)(int,int)中的*可以去掉，也可以加很多个，*就是辅助理解的
//							
//	printf("%d\n", num);
//	printf("%d\n", num2);
//
//
//	return 0;
//}

//两段有趣的代码
//（1）函数调用
//int main()
//{
//	(*(void (*)())0)();//(void (*p)()是函数指针的构架，去掉指针变量名就是函数指针类型
//						//后面加个0就是强制类型转换，把0强制转换为地址
//						//再解引用，访问函数
//						//再()，调用函数
//	return 0;
//}

//（2）函数声明
//声明的函数的名字叫：signal
//signal函数有2个参数，第一个参数的类型是int
//第二个参数的类型是void(*)(int)的函数指针类型，该指针可以指向一个函数，指向的函数参数是int，返回类型是void
//signal函数的返回类型是void(*)(int)的函数指针，该指针可以指向一个函数，指向的函数参数是int，返回类型是void
//

//void (* signal(int, void(*)(int) ) )(int);
//void (*)(int) signal(int, void(*)(int));//err//这样的写法是错的，但是可以这样去理解



//typedef关键字
			//typedef是用来进行类别重命名的，当一个类型名很复杂时，可以用typedef来简化
//整型（普通）指针重命名
//typedef int* i;
//int main()
//{
//	int a = 0;
//	//int* p = &a;//如果觉得int*麻烦，就可以重命名
//	i p2 = &a;
//
//	return 0;
//}

//特殊指针重命名（数组指针，函数指针）
//（1）数组指针重命名
//typedef int(*)[5] parr_t;//这个会报错，因为特殊指针的类型重命名要把重命名加到*右边
//typedef int(*parr_t)[5];
//int main()
//{
//	int arr[5] = { 0 };
//	//int(*p)[5] = &arr;//这时候我觉得int(*)[5]这个类型太复杂了，我选择重命名为parr_t
//	//int(*)[5]是数组指针的类型
//	parr_t p = &arr;
//	return 0;
//}

//（2）函数指针重命名
//typedef int (*p)(int, int) pf_t;//err，因为函数指针要特殊化处理
//typedef int (*pf_t)(int, int);//还是要写在*右边
//int Add(int x, int y)
//{
//	return 0;
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	int num = Add(a,b);
//	int (*p)(int, int) = &Add;//函数指针类型是int (*)(int, int)，但是太复杂了，我要把它改为pf_t
//
//	return 0;
//}

////现在就可以用typedef来简化这个函数声明
//void (* signal(int, void(*)(int) ) )(int);
////void (*)(int) signal(int, void(*)(int));//err
//typedef void (*pf_t)(int);
////简化后：
//pf_t signal(int, pf_t);

//#define进行类型重命名

//typedef int* p_t;
//
//#define P_T int*
//
//int main()
//{
//	int a = 0;
//	//int* p = &a;//指针类型是int*
//	//我对int*进行重命名
//	p_t p;
//	P_T p;//这样重命名是有缺陷🙃
//
//	//比如：
//
//	p_t p, p2;//p，p2都是整型指针类型的变量
//	P_T p3, p4;//p3是整型指针类型的变量，但p4不是，p4是整型
//				//因为：int* p3,p4;时，*拿给p3用，p4就没有*了，就变成了整型类型
//	return 0;
//}



//<6>函数指针数组
//Add(int x,int y)
//{
//	return x + y;
//}
//Sub(int x, int y)
//{
//	return x - y;
//}
//Mul(int x, int y)
//{
//	return x * y;
//}
//Div(int x, int y)
//{
//	return x / y;
//}
//
//int main()
//{
//	int a = 0;
//	int b = 0;
//	//创建指针变量，存放函数地址
//	/*int (*pf1)(int, int) = Add(a, b);
//	int (*pf2)(int, int) = Sub(a, b);
//	int (*pf3)(int, int) = Mul(a, b);
//	int (*pf4)(int, int) = Div(a, b);*/
//
//	//但是这样存放太冗余了
//	//可以使用函数指针数组存放
//	//数组是存放相同类型的数据的空间，指针数组就是存放指针的数组，那么函数指针数组就是存放函数指针的数组
//	//存放函数指针的数组是需要条件的：函数类型相同，函数形参类型及个数相同
//	int (*pfarr[4])(int, int) = {Add,Sub,Mul,Div};//函数指针数组的初始化
//	//注：指针数组是在指针类型的基础上进行初始化的
//	/*int* p1;
//	int* p2;
//	int* p3;
//	int* arr[3] = { p1,p2,p3 };*/
//
//	//那么函数指针数组就是在函数指针的类型基础上进行初始化的
//	//比如Add函数
//	//int (*p)(int, int) = &Add;
//	//int (*pfarr[4])(int, int) = { Add,Sub,Mul,Div };
//
//
//	//函数指针数组的使用
//	int i = 0;
//	for (i = 0; i < 4; i++)
//	{
//		int num = pfarr[i](8,4);//指针的使用是通过解引用*，而数组的使用是通过数组名 + 常量值来使用，后面的（）是用来调用函数的
//		printf("%d\n", num);
//	}
//
//
//	//总结：指针有很多种，数组也有很多种，指针和数组要区分开
//			//对于数组的构建元素：类型 + 数组名 + 常量值
//			//那么就可以知道整型（普通）数组：类型（对象类型） + 数组名（自取） + 常量值（自取）
//			//指针数组：类型（int*,char*等指针类型） + 数组名（自取） + 常量值（自取）
//			//函数指针数组：类型（函数指针类型，比如：int (*p)(int,int)）+ 数组名(自取) +常量值（自取） 
//
//	return 0;
//}
//对于数组的初始化，数组的使用，指针的初始化，指针的使用，可以专门总结区分	

//<7>使用函数指针数组写一个两位整数的加减乘除计算器

//这是调用函数写出的一个计算器，代码冗余，如果要扩展功能，代码将会更加冗余
//int Add(int x, int y)
//{
//	return x + y;
//}
//int Sub(int x, int y)
//{
//	return x - y;
//}
//int Mul(int x, int y)
//{
//	return x * y;
//}
//int Div(int x, int y)
//{
//	return x / y;
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	printf("**************************************\n");
//	printf("***********1.Add********2.Sub*********\n");
//	printf("***********3.Mul********4.Div*********\n");
//	printf("***********0.exit*********************\n");
//	printf("**************************************\n");
//	//int (*pfarr[4])(int, int) = {Add,Sub,Mul,Div};
//	int input = 0;
//	int num = 0;
//	do
//	{
//		printf("请选择要计算的方式：\n");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			printf("请输入两个整数进行计算\n");
//			scanf("%d %d", &a, &b);
//			num = Add(a, b);//case1~4语句不能都写int num = 调用函数;不然就会是多次初始化
//							//只能进行一次初始化，但可以多次赋值
//			printf("计算的结果是：%d\n", num);
//			break;
//		case 2:
//			printf("请输入两个整数进行计算");
//			scanf("%d %d", &a, &b);
//			num = Sub(a, b);
//			printf("计算的结果是：%d\n", num);
//			break;
//		case 3:
//			printf("请输入两个整数进行计算");
//			scanf("%d %d", &a, &b);
//			num = Mul(a, b);
//			printf("计算的结果是：%d\n", num);
//			break;
//		case 4:
//			printf("请输入两个整数进行计算");
//			scanf("%d %d", &a, &b);
//			num = Div(a, b);
//			printf("计算的结果是：%d\n", num);
//			break;
//		case 0:
//			printf("计算结束\n");
//			break;
//		default:
//			printf("输入错误，请重新输入！\n");
//			break;
//		}
//	} while (input);
//
//	return 0;
//}

//使用函数指针数组写计算器，就会更简易，并且扩展功能时代码添加也会更简易
//int Add(int x, int y)
//{
//	return x + y;
//}
//int Sub(int x, int y)
//{
//	return x - y;
//}
//int Mul(int x, int y)
//{
//	return x * y;
//}
//int Div(int x, int y)
//{
//	return x / y;
//}
//void menu()
//{
//	printf("**************************************\n");
//	printf("***********1.Add********2.Sub*********\n");
//	printf("***********3.Mul********4.Div*********\n");
//	printf("***********0.exit*********************\n");
//	printf("**************************************\n");
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	int num = 0;
//	menu();
//	int(*pfarr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//函数指针数组的初始化
//	//这时候会发现：数组下标从0开始访问元素，Add下标为0，与输入input以及菜单不一致，所以可以加一个元素，填充下标0
//	int input = 0;
//	do
//	{
//		printf("请选择要计算的方式：\n");
//		scanf("%d", &input);
//		if (1 <= input && input <= 4)
//		{
//			pfarr[input](a, b);//函数指针数组的使用：1，数组的使用只需要数组名 + 常量值，不需要说明数组的类型，而函数指针数组的类型是int(*)(int,int)，所以这边不需要*，*是类型的内容
//												//2，函数指针数组的使用要调用函数的，所以后面加上(函数实参......)
//								//总结：想要求得什么对象的类型，就把该对象名去掉，就是该对象的类型
//								//总结：函数的调用一定有后面的(函数实参......)
//			printf("请输入两个整数进行计算\n");
//			scanf("%d %d", &a, &b);
//			num = pfarr[input](a, b);//接收函数的返回值
//			printf("计算的结果是：%d\n", num);
//		}
//		else
//		{
//			if(input == 0)//判断相等，而不是赋值
//			{
//				printf("计算结束！\n");
//			}
//			else
//			{
//				printf("输入错误，请重新输入！\n");
//			}
//		}
//		
//	} while (input);
//
//	return 0;
//}
//总结：想要初始化什么对象，就想想该对象的原始形态是怎么初始化的，包含什么要素
		//同理：想要使用什么对象，就想想该对象的原始形态是怎么使用的，包含什么要素

//也可以使用函数指针来简化代码
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("**************************************\n");
	printf("***********1.Add********2.Sub*********\n");
	printf("***********3.Mul********4.Div*********\n");
	printf("***********0.exit*********************\n");
	printf("**************************************\n");
}
void calc(int (*p)(int,int))
{
	int num = 0;
	int a = 0;
	int b = 0;
	printf("请输入两个整数进行计算");
	scanf("%d %d", &a, &b);
	num = p(a, b);//p就是函数地址，Add也是函数地址，相等，不用*
					//这里涉及到函数调用的使用
	printf("计算的结果是：%d\n", num);
}
int main()
{
	menu();
	int input = 0;
	//会发现：case语句多处其实是重复的，可以对此进行简化
			//简化方法：要使用一个函数时：写一个函数，简化重复的代码，实现重复的代码的功能，并通过传递的参数使用调用函数的地址，然后进行访问
	do
	{
		printf("请选择要计算的方式：\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("计算结束\n");
			break;
		default:
			printf("输入错误，请重新输入！\n");
			break;
		}
	} while (input);

	return 0;
}